#include "dialogfileloadctrl.h"
#include "ui_dialogfileloadctrl.h"
#include <QFileDialog>
#include <QSettings>
#include <QFile>
#include <thread>
#include <QThread>
#include <QProcess>
#include <atomic>
DialogFileLoadCtrl::DialogFileLoadCtrl(QWidget *parent)
	: QDialog(parent)
	, ui(new Ui::DialogFileLoadCtrl)
	, m_pMsgMd(new QStandardItemModel(this))
{
	ui->setupUi(this);
	m_nTimer = startTimer(10000);
	loadSettings();
	ui->listView_msg->setModel(m_pMsgMd);
	slot_msg(tr("Program Started"));
	setWindowFlag(Qt::WindowMinMaxButtonsHint,true);
}

DialogFileLoadCtrl::~DialogFileLoadCtrl()
{
	delete ui;
}

void DialogFileLoadCtrl::timerEvent(QTimerEvent * evt)
{
	if (evt->timerId()==m_nTimer && ui->checkBox_watch->isChecked())
	{
		if (ui->lineEdit_src_dir->text().trimmed().length()<2)
			return;
		slot_next_prg(-1);
		slot_msg(tr("Start maintaining."));
		killTimer(m_nTimer);
		m_nTimer = -1;
		updateMap();
		cleanFile();
		m_nTimer = startTimer(10000);
		slot_msg(tr("Completed."));
		slot_next_prg(0);
	}
}

void DialogFileLoadCtrl::cleanFile()
{
	quint8 cm = 0;
	while (m_total_size > ui->doubleSpinBox_maxSz->value()*1024ll*1024ll*1024ll)
	{
		if (!m_cache_files.size())
			break;
		qint64 tmf = m_cache_files.begin()->first;
		QMap<QString,QFileInfo> & mp = m_cache_files[tmf];
		QMap<QString,qint64> & ms = m_cache_sizes[tmf];
		if (!mp.size())
		{
			m_cache_files.erase(tmf);
			m_cache_sizes.erase(tmf);
		}
		else
		{
			QString  fm = mp.begin().key();
			QFileInfo  ifi = mp.begin().value();
			assert(m_total_size >= ifi.size());
			if (false==QFile::remove(fm))
			{
				QString msg = tr("Force Deleting %1.").arg(fm);
				//Force Delete Using https://github.com/michaelknigge/forcedel
				for (int ntr = 0; ntr < 10 && QFileInfo::exists(fm); ++ntr)
				{
					QThread::msleep(200);
					QStringList args;
#ifdef __gnu_linux__
					args<<"-f";
					args<<fm;
					QProcess::execute("rm",args);
#else
					args<<fm;
					QProcess::execute("ForceDel.exe",args);
#endif
				}
				slot_msg(msg);
			}
			if (!QFileInfo::exists(fm))
			{
				m_total_size -= ifi.size();
				QString msg = tr("Succ Deleting %1.").arg(fm);
				slot_msg(msg);
			}
			else
			{
				QString msg = tr("Fail Deleting %1.").arg(fm);
				slot_msg(msg);
			}
			mp.remove(fm);
			ms.remove(fm);
			if (!mp.size())
			{
				m_cache_files.erase(tmf);
				m_cache_sizes.erase(tmf);
			}

		}
		if (++cm % 10==0)
			slot_next_prg(0);
	}
}

void DialogFileLoadCtrl::updateMap()
{
	QFileInfoList lst = enumFiles(ui->lineEdit_src_dir->text());
	m_total_size = 0;
	m_total_files = 0;
	const qint64 bfMin = ui->doubleSpinBox_bkMinSz->value()*1e6+.5;
	const qint64 bfMax = ui->doubleSpinBox_bkMaxSz->value()*1e6+.5;
	const QStringList FMLst = ui->lineEdit_dst_filetypes->text().split("|",Qt::SkipEmptyParts);
	QSet<QString> FMUpper;
	foreach(QString ex, FMLst)
	{
		QString r = ex.toUpper().trimmed();
		if (r.length())
			FMUpper << r;
	}

	foreach (QFileInfo i, lst)
	{
		QString fm = i.absoluteFilePath();
		long long sz = i.size();
		qint64 tm = i.fileTime(QFile::FileBirthTime).toMSecsSinceEpoch();
		//Judge Whether this file need to be copied.
		bool NeedTrans = false;
		//1. Type
		QString strExt = i.suffix().toUpper().trimmed();
		if (strExt.length()<1)
			strExt=":";
		if (FMUpper.contains(strExt) || FMUpper.isEmpty())
		{
			//2. Size Range
			if (sz>=bfMin && sz<=bfMax)
			{
				//3.First Descovered
				if (!m_cache_files[tm].contains(fm))
					NeedTrans = true;
				//4.Size Changed
				else if (m_cache_sizes[tm][fm]!=sz)
					NeedTrans = true;
			}
		}
		if (NeedTrans)
			TransFile(i);

		m_cache_files[tm][fm] = i;
		m_cache_sizes[tm][fm] = sz;
		m_total_size += sz;
		++m_total_files;
	}

	QString msg = tr("Enuming %1 Files %2 Bytes.").arg(m_total_files).arg(m_total_size);
	slot_next_prg(0);
	slot_msg(msg);
}

void DialogFileLoadCtrl::TransFile(QFileInfo ifile)
{
	if (ui->lineEdit_dst_dir->text().length()<2)
		return;
	QDir dirSrc(ui->lineEdit_src_dir->text());
	//Relative Path
	QString srcFile = dirSrc.relativeFilePath(ifile.absoluteFilePath());
	QString relDir = dirSrc.relativeFilePath(ifile.absolutePath());
	//Make destin path
	QDir dirDst(ui->lineEdit_dst_dir->text());
	QString dstDir = dirDst.absoluteFilePath(relDir);
	QString dstFile = dirDst.absoluteFilePath(srcFile);

	//mkpath
	QFileInfo info(dstDir);
	if (!info.exists())
		dirDst.mkpath(dstDir);
	//File
	QFileInfo infof(dstFile);
	if (infof.size()!=ifile.size())
	{
		//Copy
		QFile::remove(dstFile);
		//Copy thread in line
		QString msg;
		std::atomic<bool> bOk (false);
		std::thread t([&]()->void{
			if (true==QFile::copy(ifile.absoluteFilePath(),dstFile))
				msg = tr("Succ Copy %1 to %2.").arg(ifile.absoluteFilePath()).arg(dstFile);
			else
				msg = tr("Busy File %1 with %2 Bytes.").arg( ifile.absoluteFilePath()).arg(ifile.size());
			bOk = true;
		});
		//Hold and wait
		slot_next_prg(0);
		quint8 trc = 0;
		while (!bOk)
		{
			QThread::msleep(1);
			if (++trc % 100==0)
				slot_next_prg(0);
		}
		t.join();
		slot_msg(msg);
	}
}

QFileInfoList DialogFileLoadCtrl::enumFiles(QString dirS)
{
	QStringList lst;
	lst<< "*.*";
	lst<< "*";
	QDir dir(dirS);
	dir.makeAbsolute();
	QFileInfoList linfo = dir.entryInfoList(lst);
	QFileInfoList lstRes;
	foreach(QFileInfo i, linfo)
	{
		if (i.isDir())
		{
			QString dn = i.fileName();

			if (dn=="." ||dn==".." )
				continue;
			QFileInfoList lst_sub = enumFiles(i.absoluteFilePath());
			std::copy(lst_sub.begin(),lst_sub.end(),std::back_inserter(lstRes));
		}
		else
			lstRes << i;
	}
	slot_next_prg(0);
	return lstRes;
}

void DialogFileLoadCtrl::slot_msg(QString s)
{
	QDateTime dtm = QDateTime::currentDateTime();
	QString dtms = dtm.toString("yyyy-MM-dd HH:mm:ss");
	QString m = dtms +">" + s;
	m_pMsgMd->appendRow(new QStandardItem(m));
	if (m_pMsgMd->rowCount()>256)
		m_pMsgMd->removeRows(0,m_pMsgMd->rowCount()-256);
	ui->listView_msg->scrollToBottom();
	QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}

void DialogFileLoadCtrl::on_pushButton_br_src_clicked()
{
	QString dirS = QFileDialog::getExistingDirectory(this,tr("Watching root"),ui->lineEdit_src_dir->text());
	if (dirS.length()<2)
		return;
	QFileInfo info(dirS);
	ui->lineEdit_src_dir->setText(info.absoluteFilePath());
	saveSettings();
}

void DialogFileLoadCtrl::on_pushButton_br_dst_clicked()
{
	QString dirS = QFileDialog::getExistingDirectory(this,tr("backup root"),ui->lineEdit_dst_dir->text());
	if (dirS.length()<2)
		return;
	ui->lineEdit_dst_dir->setText(dirS);
	saveSettings();
}

void DialogFileLoadCtrl::loadSettings()
{
	QSettings settings(QCoreApplication::applicationFilePath()+".ini",QSettings::IniFormat);
	const QString lineEdit_src_dir = settings.value("ui/lineEdit_src_dir","").toString();
	ui->lineEdit_src_dir->setText(lineEdit_src_dir);

	const QString lineEdit_dst_dir = settings.value("ui/lineEdit_dst_dir","").toString();
	ui->lineEdit_dst_dir->setText(lineEdit_dst_dir);

	const QString lineEdit_dst_filetypes = settings.value("ui/lineEdit_dst_filetypes","bmp|Jpeg|Avi|Mpeg|Jpg|PNG|Tif|tiff").toString();
	ui->lineEdit_dst_filetypes->setText(lineEdit_dst_filetypes);

	const bool checkBox_watch = settings.value("ui/checkBox_watch",false).toBool();
	ui->checkBox_watch->setChecked(checkBox_watch);

	const double doubleSpinBox_maxSz = settings.value("ui/doubleSpinBox_maxSz",16).toDouble();
	ui->doubleSpinBox_maxSz->setValue(doubleSpinBox_maxSz);

	const double doubleSpinBox_bkMaxSz = settings.value("ui/doubleSpinBox_bkMaxSz",2000000).toDouble();
	ui->doubleSpinBox_bkMaxSz->setValue(doubleSpinBox_bkMaxSz);

	const double doubleSpinBox_bkMinSz = settings.value("ui/doubleSpinBox_bkMinSz",0).toDouble();
	ui->doubleSpinBox_bkMinSz->setValue(doubleSpinBox_bkMinSz);

}
void DialogFileLoadCtrl::saveSettings()
{
	QSettings settings(QCoreApplication::applicationFilePath()+".ini",QSettings::IniFormat);
	const QString lineEdit_src_dir = ui->lineEdit_src_dir->text();
	settings.setValue("ui/lineEdit_src_dir",lineEdit_src_dir);

	const QString lineEdit_dst_dir = ui->lineEdit_dst_dir->text();
	settings.setValue("ui/lineEdit_dst_dir",lineEdit_dst_dir);

	const QString lineEdit_dst_filetypes = ui->lineEdit_dst_filetypes->text();
	settings.setValue("ui/lineEdit_dst_filetypes",lineEdit_dst_filetypes);

	const bool checkBox_watch = ui->checkBox_watch->isChecked();
	settings.setValue("ui/checkBox_watch",checkBox_watch);

	const double doubleSpinBox_maxSz = ui->doubleSpinBox_maxSz->value();
	settings.setValue("ui/doubleSpinBox_maxSz",doubleSpinBox_maxSz);

	const double doubleSpinBox_bkMaxSz = ui->doubleSpinBox_bkMaxSz->value();
	settings.setValue("ui/doubleSpinBox_bkMaxSz",doubleSpinBox_bkMaxSz);

	const double doubleSpinBox_bkMinSz = ui->doubleSpinBox_bkMinSz->value();
	settings.setValue("ui/doubleSpinBox_bkMinSz",doubleSpinBox_bkMinSz);

}

void DialogFileLoadCtrl::on_checkBox_watch_clicked()
{
	saveSettings();
	slot_next_prg(ui->checkBox_watch->isChecked()?0:-1);
}

void DialogFileLoadCtrl::slot_next_prg(int n)
{
	static char prg_val [] = ".-/|\\";
	static unsigned char ps = 0;
	if (n<0)
		ui->label_prg->setText(QString(prg_val[0]));
	else
		ui->label_prg->setText(QString(prg_val[++ps%4+1]));
	QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);

}
